#include "gui.h"
#include "rsrc.h"
#include <commctrl.h>
#include <string.h>
#include <ctype.h>
#include "core.h"
#include "calc.h"

#include "gifhandle.h"
#include "gif.h"

#include "rsrc.h"
#include "var.h"
#include "link.h"
#include "keys.h"

#include "guidebug.h"
#include "guioptions.h"
#include "guicontext.h"
#include "guibuttons.h"
#include "guilcd.h"
#include "guivartree.h"
#include "dbmem.h"
#include "dbreg.h"
#include "dbtoolbar.h"
#include "dbsubbar.h"
#include "dbdisasm.h"
#include "registry.h"
#include "sendfiles.h"
#include "wbded.h"
#include <math.h>
#include "guicutout.h"
#include "uxtheme.h"
#include <windowsx.h>

#include "expandpane.h"

#define MENU_FILE 0
#define MENU_EDIT 1
#define MENU_CALC 2
#define MENU_HELP 3

char ExeDir[512];

INT_PTR CALLBACK DlgVarlist(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
HINSTANCE g_hInst;
HACCEL haccelmain;
HACCEL hacceldebug;
POINT drop_pt;
BOOL gif_anim_advance;
BOOL silent_mode = FALSE;

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK ToolProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);

void RegisterDropWindow(HWND hwnd,   IDropTarget **ppDropTarget);
void UnregisterDropWindow(HWND hwnd, IDropTarget *pDropTarget);

void gui_draw(int slot) {
	gslot = slot;
	InvalidateRect(calcs[slot].hwndLCD, NULL, FALSE);
	UpdateWindow(calcs[slot].hwndLCD);
	if (calcs[gslot].gif_disp_state != GDS_IDLE) {
		static int skip = 0;
		if (skip == 0) {
			gif_anim_advance = TRUE;
			InvalidateRect(calcs[gslot].hwndFrame, NULL, FALSE);
		}
		skip = (skip + 1) % 4;
	}
}

VOID CALLBACK TimerProc(HWND hwnd, UINT Message, UINT_PTR idEvent, DWORD dwTimer) {
	static long difference;
	static DWORD prevTimer;
	
		// How different the timer is from where it should be
		// guard from erroneous timer calls with an upper bound
		// that's the limit of time it will take before the
		// calc gives up and claims it lost time
		difference += ((dwTimer - prevTimer) & 0x003F) - TPF;
		
		// Are we greater than Ticks Per Frame that would call for
		// a frame skip?
		if (difference > -TPF) {
			calc_run_all();
			while (difference >= TPF) {
				calc_run_all();
				difference -= TPF;
			}

			int i;
			for (i = 0; i < MAX_CALCS; i++) {
				if (calcs[i].active) {
					gui_draw(i);
				}
			}
		// Frame skip if we're too far ahead.
		} else difference += TPF;

	/*
	else if (calcs[idEvent].send == TRUE) {
		static int frameskip = 0;
		if (frameskip == 0) {
			InvalidateRect(hwnd, NULL, FALSE);
			UpdateWindow(hwnd);
		}
		frameskip = (frameskip+1)%3;
	}*/

		
	prevTimer = dwTimer;
}


extern RECT db_rect;

int gui_debug(int slot) {
	pausesound();
	HWND hdebug;
	RECT pos = {CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT+600, CW_USEDEFAULT+400};
	if (db_rect.left != -1) CopyRect(&pos, &db_rect);
		
	pos.right -= pos.left;
	pos.bottom -= pos.top;
	
	if ((hdebug = FindWindow(g_szDebugName, "Debugger"))) {
		SwitchToThisWindow(hdebug, TRUE);
		return -1;
	}
	calcs[gslot].running = FALSE;
	hdebug = CreateWindowEx(
		WS_EX_APPWINDOW,
		g_szDebugName,
        "Debugger", 
		WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        pos.left, pos.top, pos.right, pos.bottom,
        0, 0, g_hInst, NULL);
	return 0;
}

void SkinCutout(HWND hwnd);

int gui_frame(int slot) {
 	RECT r;
 	
 	if (calcs[slot].SkinEnabled) {
 		SetRect(&r, 0, 0, 314, 688);
 	} else {
 		SetRect(&r, 0, 0, 256, 128);
 	}
	AdjustWindowRect(&r, WS_CAPTION | WS_TILEDWINDOW, FALSE);
	r.bottom += GetSystemMetrics(SM_CYMENU);

	// Set gslot so the CreateWindow functions operate on the correct calc
	gslot = slot;
	calcs[slot].hwndFrame = CreateWindowEx(
		0, //WS_EX_APPWINDOW,
		g_szAppName,
        "Z80", 
		(WS_TILEDWINDOW |  (silent_mode ? 0 : WS_VISIBLE) | WS_CLIPCHILDREN) & ~(WS_MAXIMIZEBOX | WS_SIZEBOX),
        CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top,
        0, 0, g_hInst, NULL);
	
	int screenx, screeny;
	if (calcs[gslot].SkinEnabled) {
		screenx = LCD_X; screeny = LCD_Y;
	} else {
		screenx = (r.right - 96*2)/2;
		screeny = 0;
	}
	calcs[slot].hwndLCD = CreateWindowEx(
		0,
		g_szLCDName,
		"LCD",
		WS_VISIBLE |  WS_CHILD,
		screenx, screeny, 96*2, 64*2,
		calcs[slot].hwndFrame, (HMENU) 99, g_hInst,  NULL);
  	
	if (calcs[slot].hwndFrame == NULL || calcs[slot].hwndLCD == NULL) return -1;

	SendMessage(calcs[slot].hwndFrame, WM_SIZE, 0, 0);
	
	calcs[slot].running = TRUE;
	return 0;
}

	
char* LoadRomIntialDialog(void) {
	OPENFILENAME ofn;
	char lpstrFilter[] 	= "\
Known types ( *.sav; *.rom) \0*.sav;*.rom\0\
Save States  (*.sav)\0*.sav\0\
ROMs  (*.rom)\0*.rom\0\
All Files (*.*)\0*.*\0\0";
	char* FileName = malloc(MAX_PATH);
	ZeroMemory(&ofn, sizeof(ofn));
	ZeroMemory(FileName, MAX_PATH);
	ofn.lStructSize		= sizeof(OPENFILENAME);
	ofn.lpstrFilter		= (LPCTSTR) lpstrFilter;
	ofn.lpstrFile		= FileName;
	ofn.nMaxFile		= MAX_PATH;
	ofn.lpstrTitle		= "Wabbitemu: Please select a ROM or save state";
	ofn.Flags			= OFN_PATHMUSTEXIST | OFN_EXPLORER |
						  OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
	if (!GetOpenFileName(&ofn)) {
		free(FileName);
		return NULL;
	}
	return FileName;
}

extern HWND hwndProp;
extern RECT PropRect;
extern int PropPageLast;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpszCmdParam, int nCmdShow) {
	MSG Msg;
	WNDCLASSEX wc;
	LPWSTR *argv;
	int argc;
	char tmpstring[512];
	BOOL loadfiles = FALSE;
	int length,i;
	
	length = GetModuleFileName(NULL,ExeDir,512);

	if (length) {
		for(i=length;i>0 && (ExeDir[i]!='\\') ;i--);
		if (i) ExeDir[i+1] = 0;
	}


	

	argv = CommandLineToArgvW(GetCommandLineW(),&argc);

	if (argv && argc>1) {
		wcstombs(tmpstring,argv[1],512);
		if ( (tmpstring[0]=='-') && (tmpstring[1]=='n') ) loadfiles = TRUE;
		else {
			HWND Findhwnd = FindWindow(g_szAppName, NULL);		
			if (Findhwnd == NULL) {
				loadfiles = TRUE;
			} else {
				HWND FindChildhwnd = FindWindowEx(Findhwnd,NULL,g_szLCDName,NULL);		
				if (FindChildhwnd == NULL) {
					loadfiles = TRUE;
				} else {
					COPYDATASTRUCT cds;
					char* FileNames = NULL;
					for(i=1;i<argc;i++) {
						memset(tmpstring,0,512);
						wcstombs(tmpstring,argv[i],512);
						if (tmpstring[0]!='-') {
							printf("%s \n",tmpstring);
							FileNames = AppendName(FileNames,tmpstring);
						} else {
							if (toupper(tmpstring[1])=='F')
								SwitchToThisWindow(FindChildhwnd, TRUE);
						}
					}
					i = SizeofFileList(FileNames);
					if (i>0) {
						cds.dwData = SEND_CUR;
						cds.cbData = i;
						cds.lpData = FileNames;
						SendMessage(FindChildhwnd, WM_COPYDATA, (WPARAM)NULL, (LPARAM)&cds);
						free(FileNames);
					}
					exit(0);
				}
			}
		}
	}

    g_hInst = hInstance;

	wc.cbSize = sizeof(wc);
	wc.style = CS_OWNDC;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(g_hInst, "W");
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = NULL;
	wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN_MENU);
	wc.lpszClassName = g_szAppName;
	wc.hIconSm = LoadIcon(g_hInst, "W");
	
	RegisterClassEx(&wc);
	
	// LCD
	wc.lpszClassName = g_szLCDName;
	wc.lpfnWndProc = LCDProc;
	wc.lpszMenuName = NULL;
	RegisterClassEx(&wc);

	// Toolbar
	wc.lpszClassName = g_szToolbar;
	wc.lpfnWndProc = ToolBarProc;
	wc.lpszMenuName = NULL;
	wc.style = 0;
	RegisterClassEx(&wc);
	
	// subbar
	wc.lpszClassName = g_szSubbar;
	wc.lpfnWndProc = SubBarProc;
	RegisterClassEx(&wc);
	
	wc.lpfnWndProc = DebugProc;
	wc.style = CS_DBLCLKS;
	wc.lpszClassName = g_szDebugName;
	wc.lpszMenuName = NULL; //MAKEINTRESOURCE(IDR_DB_MENU);
	wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE+1);
	RegisterClassEx(&wc);
	
	wc.lpszMenuName = NULL;
	wc.style = CS_OWNDC | CS_HREDRAW;
	wc.lpfnWndProc = DisasmProc;
	wc.lpszClassName = g_szDisasmName;
	wc.hbrBackground = (HBRUSH) NULL;
	RegisterClassEx(&wc);
	
	// Registers
	wc.style = CS_DBLCLKS;
	wc.lpfnWndProc = RegProc;
	wc.lpszClassName = g_szRegName;
	wc.hbrBackground = NULL;
	RegisterClassEx(&wc);
	
	wc.style = 0;
	wc.lpfnWndProc = ExpandPaneProc;
	wc.lpszClassName = g_szExpandPane;
	RegisterClassEx(&wc);

	wc.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = MemProc;
	wc.lpszClassName = g_szMemName;
	wc.hbrBackground = NULL;
	RegisterClassEx(&wc);
	
	// initialize com events
	OleInitialize(0);
	RegisterClassObject();
	
	if (argv && argc>1) {
		for (i=1;i<argc;i++) {
			memset(tmpstring,0,512);
			wcstombs(tmpstring,argv[i],512);
			if (tmpstring[0] == '/' || tmpstring[0] == '-') {
				if (toupper(tmpstring[1]) == 'S') {
					silent_mode = TRUE;
				}
			}
		}
	}
	
	int slot = calc_slot_new();
	LoadRegistrySettings();

	slot = rom_load(slot, calcs[gslot].rom_path);
	if (slot != -1) gui_frame(slot);
	else {
		char* string =LoadRomIntialDialog();
		if (string) {
			slot = calc_slot_new();
			slot = rom_load(slot, string);
			if (slot != -1) gui_frame(slot);
			else return EXIT_FAILURE;
		} else return EXIT_FAILURE;
	}
	
	strcpy(calcs[slot].labelfn,"labels.lab");

	state_build_applist(&calcs[gslot].cpu, &calcs[gslot].applist);
	VoidLabels(slot);
	
	if (loadfiles) {
		if (argv && argc>1) {
			char* FileNames = NULL;
			for(i=1;i<argc;i++) {
				memset(tmpstring,0,512);
				wcstombs(tmpstring,argv[i],512);
				if (tmpstring[0]!='-') {
					printf("%s\n",tmpstring);
					FileNames = AppendName(FileNames,tmpstring);
				}
			}
			ThreadSend(FileNames, SEND_CUR);
		}
	}
	loadfiles = FALSE;
	

#ifdef USE_DIRECTX
	pD3D = Direct3DCreate9(D3D_SDK_VERSION);
	
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.Windowed = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferCount = 1;
	
	
	if (pD3D->lpVtbl->CreateDevice(
			pD3D,
			D3DADAPTER_DEFAULT, 
			D3DDEVTYPE_HAL, 
			calcs[gslot].hwndLCD,
			D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_FPU_PRESERVE ,
			&d3dpp,
			&pd3dDevice) != D3D_OK) {
		printf("failed to create device\n");
		exit(1);
	}
#endif
	
	// Set the one global timer for all calcs
	SetTimer(NULL, 0, TPF, TimerProc);
	
	hacceldebug = LoadAccelerators(g_hInst, "DisasmAccel");
	haccelmain = LoadAccelerators(g_hInst, "Z80Accel");	

    while (GetMessage(&Msg, NULL, 0, 0)) {
		HACCEL haccel = hacceldebug;
		HWND hwndtop = GetForegroundWindow();
		if (hwndtop) {
			if (hwndtop == FindWindow(g_szDebugName, NULL) ) {
				haccel = hacceldebug;
			} else if (hwndtop == FindWindow(g_szAppName, NULL) ) {
				haccel = haccelmain;
				hwndtop = FindWindowEx(hwndtop,NULL,g_szLCDName,NULL);
				SetForegroundWindow(hwndtop);
			}
		}
		
		if (hwndProp != NULL) {
			if (PropSheet_GetCurrentPageHwnd(hwndProp) == NULL) {
				GetWindowRect(hwndProp, &PropRect);
				DestroyWindow(hwndProp);
				hwndProp = NULL;
			}
		}

		if (hwndProp == NULL || PropSheet_IsDialogMessage(hwndProp, &Msg) == FALSE) {
			if (!TranslateAccelerator(hwndtop, haccel, &Msg)) {
				TranslateMessage(&Msg);
				int slot = calc_from_hwnd(Msg.hwnd);
				if (slot != -1) gslot = slot;
	        	DispatchMessage(&Msg);
			}
		} else {
			// Get the current tab
			HWND hwndPropTabCtrl = PropSheet_GetTabControl(hwndProp);
			PropPageLast = TabCtrl_GetCurSel(hwndPropTabCtrl);
		}
    }

    // Make sure the GIF has terminated
	if (gif_write_state == GIF_FRAME) {
		gif_write_state = GIF_END;
		handle_screenshot();
	}
	
    RevokeClassObject();
	// Shutdown COM
	OleUninitialize();

    return Msg.wParam;
}


LRESULT CALLBACK ToolProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
	return DefWindowProc(hwnd, Message, wParam, lParam);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
	static HDC hdcKeymap;
	static const RECT rectSkin = {0, 0, 314, 688};
	static POINT ctxtPt;

	switch (Message) {
		case WM_CREATE:
		{
			if (calcs[gslot].bCutout && calcs[gslot].SkinEnabled) {
				if (EnableCutout(hwnd) != 0) {
					MessageBox(NULL, "Couldn't cutout window", "error",  MB_OK);
				}
			}
			
			// Force the current skin setting to be enacted
			calcs[gslot].SkinEnabled = !calcs[gslot].SkinEnabled;
			SendMessage(hwnd, WM_COMMAND, IDM_CALC_SKIN, 0);
			
			SetWindowText(hwnd, "Wabbitemu");
			
			HDC hdc = GetDC(hwnd);
			HBITMAP hbmSkin = LoadBitmap(g_hInst, "Skin");
			calcs[gslot].hdcSkin = CreateCompatibleDC(hdc);
			SelectObject(calcs[gslot].hdcSkin, hbmSkin);
			
			// Only one keymap is needed for all calcs
			// Load it just for the first to conserve RAM
			if (calc_count() == 1) {
				HBITMAP hbmKeymap = LoadBitmap(g_hInst, "Keymap");
				hdcKeymap = CreateCompatibleDC(hdc);
				SelectObject(hdcKeymap, hbmKeymap);
			}	
			
			ReleaseDC(hwnd, hdc);
			return 0;
		}
		// Handle the tiny little buttons
		case WM_DRAWITEM: {
			DRAWITEMSTRUCT *dis = (LPDRAWITEMSTRUCT) lParam;

			HBITMAP hbmButtons = LoadBitmap(g_hInst, "close");
			HDC hdc = CreateCompatibleDC(dis->hDC);
			SelectObject(hdc, hbmButtons);
			
			UINT col, row;
			switch (dis->CtlID) {
				case 0:
					col = 0;
					break;
				case 1:
					col = 13;
					break;
				default:
					return FALSE;
			}
			
			row = 0;
			if (dis->itemState & ODS_SELECTED) row = 13;

			RECT r;
			GetWindowRect(dis->hwndItem, &r);
			POINT p;
			p.x = r.left;
			p.y = r.top;
			
			ScreenToClient(hwnd, &p);
			BitBlt(dis->hDC,0,0,13,13,calcs[gslot].hdcSkin,p.x,p.y,SRCCOPY);
			
			BLENDFUNCTION bf;
			bf.BlendOp = AC_SRC_OVER;
			bf.BlendFlags = 0;
			bf.SourceConstantAlpha = 160;
			bf.AlphaFormat = 0;		
			AlphaBlend(	dis->hDC, 0, 0, 13, 13, hdc, col, row, 13, 13, bf );

			DeleteDC(hdc);
			DeleteObject(hbmButtons);
			return TRUE;
		}
		case WM_ACTIVATE: {
			RECT rc;
			GetClientRect(hwnd, &rc);
			InvalidateRect(hwnd, &rc, FALSE);
			return 0;
		}
		case WM_PAINT:
		{
#define GIFGRAD_PEAK 15
#define GIFGRAD_TROUGH 10
			
			static int GIFGRADWIDTH = 0;
			static int GIFADD = 1;
			
			if (gif_anim_advance) {
				switch (calcs[gslot].gif_disp_state) {
				case GDS_STARTING:
					if (GIFGRADWIDTH > 15) {
						calcs[gslot].gif_disp_state = GDS_RECORDING;
						GIFADD = -1;
					} else {
						GIFGRADWIDTH ++;
					}
					break;
				case GDS_RECORDING:
					GIFGRADWIDTH += GIFADD;
					if (GIFGRADWIDTH > GIFGRAD_PEAK) GIFADD = -1;
					else if (GIFGRADWIDTH < GIFGRAD_TROUGH) GIFADD = 1;
					break;
				case GDS_ENDING:
					if (GIFGRADWIDTH) GIFGRADWIDTH--;
					else calcs[gslot].gif_disp_state = GDS_IDLE;
					break;
				case GDS_IDLE:
					break;
				}	
				gif_anim_advance = FALSE;
			}
					
			if (calcs[gslot].gif_disp_state != GDS_IDLE) {
				RECT screen;
				GetWindowRect(calcs[gslot].hwndLCD, &screen);
				
				int orig_w = screen.right - screen.left;
				int orig_h = screen.bottom - screen.top;
				
				AdjustWindowRect(&screen, WS_CAPTION, FALSE);
				screen.top -= GetSystemMetrics(SM_CYMENU);
				
				RECT rc;
				GetWindowRect(calcs[gslot].hwndFrame, &rc);
				//printf("screen: %d\n", screen.left - rc.left);
				
				SetRect(&screen, screen.left - rc.left, screen.top - rc.top,
						screen.left - rc.left + orig_w,
						screen.top - rc.top + orig_h);

				int grayred = (((double) GIFGRADWIDTH / GIFGRAD_PEAK) * 50);
				DrawGlow(GetDC(hwnd), &screen, RGB(127-grayred, 127-grayred, 127+grayred), GIFGRADWIDTH);
				
				InflateRect(&screen, GIFGRADWIDTH, GIFGRADWIDTH);
				ValidateRect(hwnd, &screen);
			}
			
			PAINTSTRUCT ps;
			HDC hdc;
			hdc = BeginPaint(hwnd, &ps);
			
			RECT rc;
			GetClientRect(hwnd, &rc);		
			
			if (calcs[gslot].SkinEnabled == TRUE)
				StretchBlt(hdc, 0, 0, rc.right, rc.bottom, calcs[gslot].hdcSkin, 
						rectSkin.left, rectSkin.top, rectSkin.right, rectSkin.bottom, SRCCOPY);
			else
				FillRect(hdc, &rc, GetStockObject(GRAY_BRUSH));
				
			if (calcs[gslot].SkinEnabled == TRUE && calcs[gslot].bCutout && GetForegroundWindow() != hwnd) {
				HDC hdcOverlay = CreateCompatibleDC(hdc);
				HBITMAP bmpGray = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
				SelectObject(hdcOverlay, bmpGray);
				FillRect(hdcOverlay, &rc, GetStockObject(GRAY_BRUSH));
				BLENDFUNCTION bf;
				bf.BlendOp = AC_SRC_OVER;
				bf.BlendFlags = 0;
				bf.SourceConstantAlpha = 110;
				bf.AlphaFormat = 0;
				AlphaBlend(	hdc, 0, 0, rc.right, rc.bottom,
							hdcOverlay, 0, 0, rc.right, rc.bottom,
							bf );
				
				DeleteObject(bmpGray);
				DeleteDC(hdcOverlay);
				
			}
			
			EndPaint(hwnd, &ps);

			return 0;
		}
		case WM_COMMAND:
		{
			switch (LOWORD(wParam)) {
				case IDM_FILE_NEW:
				{
					int slot = calc_slot_new();
					
					rom_load(slot, calcs[gslot].rom_path);
					gui_frame(slot);
					break;
				}
				case IDM_FILE_EXIT:
					if (calc_count() > 1) {
						char buf[256];
						sprintf(buf, "If you exit now, %d other running calculator(s) will be closed.  Are you sure you want to exit?", calc_count()-1);
						int res = MessageBoxA(NULL, buf, "Wabbitemu", MB_YESNO);
						if (res == IDCANCEL || res == IDNO)
							break;
						PostQuitMessage(0);
					}
					SendMessage(hwnd, WM_CLOSE, 0, 0);
					break;
				case IDM_FILE_GIF:
				{
					HMENU hmenu = GetMenu(hwnd);

					if (gif_write_state == GIF_IDLE) {
						gif_write_state = GIF_START;
						calcs[gslot].gif_disp_state = GDS_STARTING;
						CheckMenuItem(GetSubMenu(hmenu, MENU_FILE), IDM_FILE_GIF, MF_BYCOMMAND | MF_CHECKED);
					} else {
						gif_write_state = GIF_END;
						calcs[gslot].gif_disp_state = GDS_ENDING;
						CheckMenuItem(GetSubMenu(hmenu, MENU_FILE), IDM_FILE_GIF, MF_BYCOMMAND | MF_UNCHECKED);
					}
					break;
				}
				case IDM_FILE_CLOSE:
					return DestroyWindow(hwnd);
				case IDM_CALC_VARIABLES:
					CreateVarTreeList();
					break;
				case IDM_CALC_SKIN:
				{
					HMENU hmenu = GetMenu(hwnd);
					if (hmenu == NULL)
						break;
					if (calcs[gslot].SkinEnabled) {
						
						CheckMenuItem(GetSubMenu(hmenu, 2), IDM_CALC_SKIN, MF_BYCOMMAND | MF_UNCHECKED);
						// Create status bar
						RECT rc;
						SetRect(&rc, 0, 0, 256, 128);
						
						calcs[gslot].SkinEnabled = FALSE;
						
						int iStatusWidths[] = {100, -1};
						calcs[gslot].hwndStatusBar = CreateWindowEx(0, STATUSCLASSNAME, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, 99, g_hInst, NULL);
						SendMessage(calcs[gslot].hwndStatusBar, SB_SETPARTS, 2, &iStatusWidths);
						
						RECT src;
						GetWindowRect(calcs[gslot].hwndStatusBar, &src);
						AdjustWindowRect(&rc, WS_CAPTION, FALSE);
						rc.bottom += GetSystemMetrics(SM_CYMENU);
						rc.bottom += src.bottom - src.top;
						SetWindowPos(hwnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);
						SendMessage(calcs[gslot].hwndStatusBar, WM_SIZE, 0, 0);
						SendMessage(calcs[gslot].hwndStatusBar, SB_SETTEXT, 1, (LPARAM) CalcModelTxt[calcs[gslot].model]);

						if (calcs[gslot].bCutout) {
							DisableCutout(hwnd);
							calcs[gslot].bCutout = TRUE;
						}
					} else {
						CheckMenuItem(GetSubMenu(hmenu, 2), IDM_CALC_SKIN, MF_BYCOMMAND | MF_CHECKED);
						
						SendMessage(calcs[gslot].hwndStatusBar, WM_DESTROY, 0, 0);
						SendMessage(calcs[gslot].hwndStatusBar, WM_CLOSE, 0, 0);
						calcs[gslot].hwndStatusBar = NULL;
						calcs[gslot].SkinEnabled = TRUE;
						
						RECT rc;
						CopyRect(&rc, &rectSkin);
						AdjustWindowRect(&rc, WS_CAPTION, FALSE);
						rc.bottom += GetSystemMetrics(SM_CYMENU);
	
						SetWindowPos(hwnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);
						SendMessage(hwnd, WM_SIZE, 0, 0);
						InvalidateRect(hwnd, NULL, TRUE);

						if (calcs[gslot].bCutout)
							EnableCutout(hwnd);
					}
					break;
				}
				case IDM_CALC_SOUND:
				{
					HMENU hmenu = GetMenu(hwnd);
					togglesound();
					CheckMenuItem(GetSubMenu(hmenu, 2), IDM_CALC_SOUND, MF_BYCOMMAND | (calcs[gslot].audio->enabled ? MF_CHECKED : MF_UNCHECKED));
					break;
				}
				case IDM_CALC_OPTIONS:
					DoPropertySheet(NULL);
					break;
				case IDM_FILE_OPEN:
					GetOpenSendFileName(hwnd, 0);
					break;
				case IDM_FILE_SAVE:
					SaveStateDialog(hwnd);
					break;
				case IDM_EDIT_COPY:
				{
					HLOCAL ans;

					ans = (HLOCAL) GetRealAns(&calcs[gslot].cpu);
					OpenClipboard(hwnd);
					EmptyClipboard();
					SetClipboardData(CF_TEXT, ans);
					
					CloseClipboard();
					break;
				}
				case IDM_DEBUG_RESET:
					printf("reset %d\n", gslot);
					calc_reset(gslot);
					printf("start up!\n");
					calcs[gslot].cpu.pio.keypad->on_pressed |= KEY_FALSEPRESS;
					printf("run timed.\n");
					calc_run_timed(gslot, 300);
					calcs[gslot].cpu.pio.keypad->on_pressed &= ~KEY_FALSEPRESS;
					printf("ready.\n");
					break;
				case IDM_DEBUG_OPEN:
				case IDM_DEBUG:
					gui_debug(gslot);
					break;
				case IDM_HELP_ABOUT:
					ShellExecute(NULL, "open", g_szWebPage,
					    NULL, NULL, SW_SHOWNORMAL);
					break;
				case IDM_FRAME_BTOGGLE:
					SendMessage(hwnd, WM_MBUTTONDOWN, MK_MBUTTON, MAKELPARAM(ctxtPt.x, ctxtPt.y));
					break;
				case IDM_FRAME_BUNLOCK:
				{
					RECT rc;
					keypad_t *kp = (&calcs[gslot].cpu)->pio.devices[1].aux;
					int group,bit;
					GetClientRect(hwnd, &rc);		
					for(group=0;group<7;group++) {
						for(bit=0;bit<8;bit++) {
							kp->keys[group][bit] &=(~KEY_LOCKPRESS);
						}
					}
					calcs[gslot].cpu.pio.keypad->on_pressed &=(~KEY_LOCKPRESS);

					HBITMAP hbmSkin = LoadBitmap(g_hInst, "Skin");
					HBITMAP oldSkin = SelectObject(calcs[gslot].hdcSkin, hbmSkin);
					SendMessage(hwnd, WM_SIZE, 0, 0);
					DeleteObject(oldSkin);
					break;
				}
			}
			switch (HIWORD(wParam)) {
				case BN_CLICKED:
				{
					if (calcs[gslot].bCutout == FALSE) return 0;
					switch (LOWORD(wParam)) {
						case 0:
							return SendMessage(hwnd, WM_CLOSE, 0, 0);
						case 1:
							ShowWindow(hwnd, SW_MINIMIZE);
							return TRUE;
					}
					break;
				}
			}
			return 0;
		}
		//case WM_MOUSEMOVE:
		case WM_LBUTTONUP:
		case WM_LBUTTONDOWN:
		{
			int group,bit;
			static POINT pt;
			keypad_t *kp = calcs[gslot].cpu.pio.keypad;

			if (Message == WM_LBUTTONDOWN) {
				SetCapture(hwnd);
				pt.x	= GET_X_LPARAM(lParam);
				pt.y	= GET_Y_LPARAM(lParam);	
			} else {
				ReleaseCapture();
			}
	
			for(group=0;group<7;group++) {
				for(bit=0;bit<8;bit++) {
					kp->keys[group][bit] &=(~KEY_MOUSEPRESS);
				}
			}

			calcs[gslot].cpu.pio.keypad->on_pressed &= ~KEY_MOUSEPRESS;

			if (wParam != MK_LBUTTON) goto finalize_buttons;

			COLORREF c = GetPixel(hdcKeymap, pt.x, pt.y);
			if (GetRValue(c) == 0xFF) goto finalize_buttons;
			
			if ( (GetGValue(c)>>4)==0x05 && (GetBValue(c)>>4)==0x00){
				calcs[gslot].cpu.pio.keypad->on_pressed |= KEY_MOUSEPRESS;
			} else {
				kp->keys[GetGValue(c) >> 4][GetBValue(c) >> 4] |= KEY_MOUSEPRESS;
				if ((kp->keys[GetGValue(c) >> 4][GetBValue(c) >> 4] & KEY_STATEDOWN) == 0) {
					DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &pt, DBS_DOWN | DBS_PRESS);
					kp->keys[GetGValue(c) >> 4][GetBValue(c) >> 4] |= KEY_STATEDOWN;
					SendMessage(hwnd, WM_SIZE, 0, 0);
				}
			}
			
		extern POINT ButtonCenter[64];
		finalize_buttons:
			kp = calcs[gslot].cpu.pio.keypad;
			for(group=0;group<7;group++) {
				for(bit=0;bit<8;bit++) {
					if ((kp->keys[group][bit] & KEY_STATEDOWN) &&
						((kp->keys[group][bit] & KEY_MOUSEPRESS) == 0) &&
						((kp->keys[group][bit] & KEY_KEYBOARDPRESS) == 0)) {
						DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &ButtonCenter[bit+(group<<3)], DBS_UP | DBS_PRESS);
						kp->keys[group][bit] &= (~KEY_STATEDOWN);
						SendMessage(hwnd, WM_SIZE, 0, 0);
					}
				}
			}
			return 0;
		}
		case WM_MBUTTONDOWN:
		{
			int group,bit;
			POINT pt;
			keypad_t *kp = (&calcs[gslot].cpu)->pio.devices[1].aux;
 
			pt.x	= GET_X_LPARAM(lParam);
			pt.y	= GET_Y_LPARAM(lParam);	
 
			COLORREF c = GetPixel(hdcKeymap,pt.x, pt.y);
			if (GetRValue(c) == 0xFF) return 0;
			group	= GetGValue(c)>>4;
			bit		= GetBValue(c)>>4;
 
			if (group== 0x20 && bit == 0x00) {
				calcs[gslot].cpu.pio.keypad->on_pressed ^= KEY_LOCKPRESS;
				if ( calcs[gslot].cpu.pio.keypad->on_pressed &  KEY_LOCKPRESS ) {
					DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &pt, DBS_DOWN | DBS_LOCK);
				} else {
					DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &pt, DBS_LOCK | DBS_UP);
				}
			} else {
				kp->keys[group][bit] ^= KEY_LOCKPRESS;
				if (kp->keys[group][bit] &  KEY_LOCKPRESS ) {
					DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &pt, DBS_DOWN | DBS_LOCK);
				} else {
					DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &pt, DBS_LOCK | DBS_UP);
				}
 
			}
			SendMessage(hwnd, WM_SIZE, 0, 0);
			return 0;
		}
		
		case WM_KEYDOWN:
			/* make this an accel*/
			if (wParam == VK_F8) {
				calcs[gslot].warp = !calcs[gslot].warp;
			}
			
			if (wParam == VK_SHIFT) {
				if (GetKeyState(VK_LSHIFT) & 0xFF00) {
					wParam = VK_LSHIFT;
				} else {
					wParam = VK_RSHIFT;
				}
			}
			
			keyprog_t *kp = keypad_key_press(&calcs[gslot].cpu, wParam);
			if (kp) {
				extern POINT ButtonCenter[64];
				if ((calcs[gslot].cpu.pio.keypad->keys[kp->group][kp->bit] & KEY_STATEDOWN) == 0) {
					DrawButtonState(calcs[gslot].hdcSkin, hdcKeymap, &ButtonCenter[kp->bit+(kp->group<<3)], DBS_DOWN | DBS_PRESS);
					calcs[gslot].cpu.pio.keypad->keys[kp->group][kp->bit] |= KEY_STATEDOWN;
					SendMessage(hwnd, WM_SIZE, 0, 0);
					goto finalize_buttons;
				}
			}
			return 0;
		case WM_KEYUP:
			if (wParam == VK_SHIFT) {
				keypad_key_release(&calcs[gslot].cpu, VK_LSHIFT);
				keypad_key_release(&calcs[gslot].cpu, VK_RSHIFT);
			} else {
				keypad_key_release(&calcs[gslot].cpu, wParam);
			}
			goto finalize_buttons;
		case WM_SIZE:
		{
			RECT rc, screen;
			GetClientRect(hwnd, &rc);

			int desired_height;
			if (calcs[gslot].SkinEnabled)
				desired_height = 688;
			else
				desired_height = 128;
			
			double status_height;
			if (calcs[gslot].hwndStatusBar == NULL) {
				status_height = 0.0;
			} else {
				RECT src;
				GetWindowRect(calcs[gslot].hwndStatusBar, &src);
				
				status_height = src.bottom - src.top;
				desired_height += status_height;
					
			}

			rc.bottom -= status_height;
	
			double xc, yc;
			if (calcs[gslot].SkinEnabled) {
				xc = ((double)rc.right)/314.0;
				yc = ((double)rc.bottom)/(688.0);
			} else {
				xc = ((double)rc.right)/256.0;
				yc = ((double)rc.bottom)/(128.0);
			}
			SetRect(&screen, 
				0, 0,
				calcs[gslot].cpu.pio.lcd->width*2*xc,
				64*2*yc);
			
			if (calcs[gslot].SkinEnabled)
				OffsetRect(&screen, LCD_X*xc, LCD_Y*yc);
			else
				OffsetRect(&screen, ((rc.right - calcs[gslot].cpu.pio.lcd->width*2*xc)/2), 0);
			
			if ((rc.right - rc.left) & 1) rc.right++;
			if ((rc.bottom - rc.top) & 1) rc.bottom++;

			MoveWindow(calcs[gslot].hwndLCD, screen.left, screen.top, 
				screen.right-screen.left, screen.bottom-screen.top, TRUE);

			//printf("screen: %d\n", screen.right - screen.left);
			if (calcs[gslot].hwndStatusBar != NULL)
				SendMessage(calcs[gslot].hwndStatusBar, WM_SIZE, 0, 0);
			
			UpdateWindow(calcs[gslot].hwndLCD);
			InvalidateRect(hwnd, NULL, FALSE);
			ValidateRect(hwnd, &screen);
			return 0;	
		}
		case WM_CONTEXTMENU:
		{
			ctxtPt.x = GET_X_LPARAM(lParam);
			ctxtPt.y = GET_Y_LPARAM(lParam);
			
			HMENU hmenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_FRAME_MENU)); 
		    // TrackPopupMenu cannot display the menu bar so get 
		    // a handle to the first shortcut menu. 
			hmenu = GetSubMenu(hmenu, 0); 
			
			if (!OnContextMenu(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), hmenu)) {
				DefWindowProc(hwnd, Message, wParam, lParam);
			}
			ScreenToClient(hwnd, &ctxtPt);
			DestroyMenu(hmenu); 
			return 0;
		}
		//case WM_CLOSE:
		//	DestroyWindow(hwnd);
		//	return 0;
			
		case WM_DESTROY:

			// Delete the skin specific to this calc
			DeleteDC(calcs[gslot].hdcSkin);
			// If it's the last calc, delete the keymap also

			if (calc_count() == 1) {
				printf("Releasing keymap\n");
				DeleteDC(hdcKeymap);
				
				printf("Saving registry settings\n");
				SaveRegistrySettings();

			}
			
			printf("Freeing calculator slot\n");
			calc_free(gslot);
	
			if (calc_count() == 0)
				PostQuitMessage(0);
			return 0;
			
		case WM_NCHITTEST:
		{
			int htRet = DefWindowProc(hwnd, Message, wParam, lParam);
			if (htRet != HTCLIENT) return htRet;
			
			POINT pt;
			pt.x = GET_X_LPARAM(lParam);
			pt.y = GET_Y_LPARAM(lParam);
			ScreenToClient(hwnd, &pt);
			if (GetRValue(GetPixel(hdcKeymap, pt.x, pt.y)) != 0xFF) return htRet;
			return HTCAPTION;
		}
		default:
			return DefWindowProc(hwnd, Message, wParam, lParam);
	}	
	return 0;
}

